home *** CD-ROM | disk | FTP | other *** search
/ Visual Cafe 3 / Visual Cafe 3.ISO / Vcafe / JFC.bin / MacPopupMenuUI.java < prev    next >
Text File  |  1998-06-30  |  24KB  |  816 lines

  1. /*
  2.  * @(#)MacPopupMenuUI.java    1.6 98/02/02
  3.  *
  4.  * Copyright (c) 1998 Sun Microsystems, Inc. All Rights Reserved.
  5.  *
  6.  * This software is the confidential and proprietary information of Sun
  7.  * Microsystems, Inc. ("Confidential Information").  You shall not
  8.  * disclose such Confidential Information and shall use it only in
  9.  * accordance with the terms of the license agreement you entered into
  10.  * with Sun.
  11.  *
  12.  * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
  13.  * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  14.  * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  15.  * PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
  16.  * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
  17.  * THIS SOFTWARE OR ITS DERIVATIVES.
  18.  *
  19.  */
  20.  
  21. package com.sun.java.swing.plaf.mac;
  22.  
  23. import com.sun.java.swing.*;
  24. import com.sun.java.swing.plaf.*;
  25. import com.sun.java.swing.event.PopupMenuListener;
  26. import com.sun.java.swing.event.PopupMenuEvent;
  27. import com.sun.java.swing.plaf.basic.BasicPopupMenuUI;
  28.  
  29. import java.awt.*;
  30. import java.awt.event.MouseEvent;
  31. import java.awt.event.ActionListener;
  32. import java.awt.event.ActionEvent;
  33.  
  34.  
  35. /**
  36.  * A Mac L&F implementation of PopupMenuUI. 
  37.  * <p>
  38.  * Warning: serialized objects of this class will not be compatible with
  39.  * future swing releases.  The current serialization support is appropriate
  40.  * for short term storage or RMI between Swing1.0 applications.  It will
  41.  * not be possible to load serialized Swing1.0 objects with future releases
  42.  * of Swing.  The JDK1.2 release of Swing will be the compatibility
  43.  * baseline for the serialized form of Swing objects.
  44.  *
  45.  * @version @(#)MacPopupMenuUI.java    1.0 01/20/98
  46.  * @author Symantec
  47.  */
  48. public class MacPopupMenuUI extends BasicPopupMenuUI implements PopupMenuListener {
  49.  
  50.     LayoutManager2            originalLayoutManager;
  51.  
  52.     MacPopupMenuLayout            scrollerLayoutManager = new MacPopupMenuLayout();
  53.     PopupUpScrollerMenuItem        upScroller = new PopupUpScrollerMenuItem();
  54.     PopupDownScrollerMenuItem    downScroller = new PopupDownScrollerMenuItem();
  55.  
  56.         //
  57.         //    Variables for determining what items are visible within the scrollable popup menu
  58.         //
  59.     Rectangle                constrainedRect = null;
  60.     int                        minY = 0;
  61.     int                        maxY = 0;
  62.     int                        firstRealMenuItem = 0;
  63.     int                        lastRealMenuItem = 0;
  64.  
  65.     int                        nonVisibleXAdjustment = 500;
  66.     int                        yAdjustment = 0;
  67.     int                        topVisibleMenuItem = 0;
  68.     int                        bottomVisibleMenuItem = -1;
  69.     int                        selectedMenuItem = -1;
  70.  
  71.  
  72.     public static ComponentUI createUI(JComponent c) {
  73.         return new MacPopupMenuUI();
  74.     }
  75.  
  76.     public void installUI(JComponent c) {
  77.         super.installUI(c);
  78.  
  79.         /*
  80.          * Install a layout manager that knows about up and down arrows in
  81.          * the popup menu, and sizing it to fit.
  82.          */
  83.         LayoutManager layout = popupMenu.getLayout();
  84.         if (layout instanceof LayoutManager2) {
  85.             originalLayoutManager = (LayoutManager2) layout;
  86.             popupMenu.setLayout(scrollerLayoutManager);
  87.         }
  88.         
  89.         popupMenu.addPopupMenuListener(this);
  90.     }
  91.  
  92.     public void uninstallUI(JComponent c) {
  93.         /*
  94.          * Remove all of our extras
  95.          */
  96.         upScroller.stopScroller();
  97.         downScroller.stopScroller();
  98.  
  99.         popupMenu.remove(upScroller);
  100.         popupMenu.remove(downScroller);
  101.         popupMenu.removePopupMenuListener(this);
  102.  
  103.         /*
  104.          * Re-install the regular layout manager
  105.          */        
  106.         popupMenu.setLayout(originalLayoutManager);
  107.  
  108.         super.uninstallUI(c);
  109.     }
  110.  
  111.  
  112.  
  113.     public Dimension getMinimumSize(JComponent c) {
  114.         return getPreferredSize(c);
  115.     }
  116.  
  117.     public Dimension getPreferredSize(JComponent c) {
  118.         if (constrainedRect != null)
  119.             return constrainedRect.getSize();
  120.  
  121.         Dimension superPreferredSize = c.preferredSize();
  122.         return superPreferredSize;
  123.     }
  124.  
  125.     public Dimension getMaximumSize(JComponent c) {
  126.         return getPreferredSize(c);
  127.     }
  128.  
  129.  
  130.     void getPopupConstraints(Point preferredLocation, Rectangle maxBounds) {
  131.         Component invoker = popupMenu.getInvoker();
  132.         if (invoker == null) return;
  133.  
  134.         Container parent;
  135.         for (parent = invoker.getParent(); parent != null ; parent = parent.getParent()) {
  136.             if (parent instanceof JFrame || parent instanceof JDialog) {
  137.                 maxBounds.setBounds(parent.getBounds());
  138.                 break;
  139.             } else if (parent instanceof JApplet) {
  140.                 Rectangle r = parent.getBounds();
  141.                 Point p = parent.getLocationOnScreen();
  142.  
  143.                 maxBounds.setBounds(p.x, p.y, r.width, r.height);
  144.                 break;
  145.             } else if (parent instanceof java.awt.Frame) {
  146.                 maxBounds.setBounds(parent.getBounds());
  147.                 break;
  148.             }
  149.         }
  150.  
  151.         parent = invoker.getParent();
  152.         preferredLocation.setLocation(invoker.getLocationOnScreen());
  153.  
  154.         if (invoker instanceof JMenu)
  155.         {
  156.             Dimension s = invoker.getSize();
  157.             if (parent instanceof JPopupMenu)
  158.             {
  159.                 preferredLocation.x += s.width;                // (Pull Right)
  160.             }
  161.             else if (parent instanceof JMenuBar)
  162.             {
  163.                 preferredLocation.y += s.height;            // (Pull Down)
  164.                 
  165.                 // Constrain the maxBounds so the menu won't go above the menubar
  166.                 maxBounds.height -= (preferredLocation.y - maxBounds.y);
  167.                 maxBounds.y = preferredLocation.y;
  168.             }
  169.         }
  170.         else if (invoker instanceof JComboBox)
  171.         {                                                    // (Pop Up)
  172.         }
  173.     }
  174.  
  175.     void setTopVisibleMenuItem(int menuItem) {
  176.         if (menuItem < 0)
  177.             topVisibleMenuItem = 0;
  178.         else if (menuItem > popupMenu.getComponents().length - 1)
  179.             topVisibleMenuItem = popupMenu.getComponents().length - 1;
  180.         else
  181.             topVisibleMenuItem = menuItem;
  182.         bottomVisibleMenuItem = -1;
  183.         selectedMenuItem = -1;
  184.     }
  185.  
  186.     void setBottomVisibleMenuItem(int menuItem) {
  187.         topVisibleMenuItem = -1;
  188.         if (menuItem < 0)
  189.             bottomVisibleMenuItem = 0;
  190.         else if (bottomVisibleMenuItem > popupMenu.getComponents().length - 1)
  191.             bottomVisibleMenuItem = popupMenu.getComponents().length - 1;
  192.         else
  193.             bottomVisibleMenuItem = menuItem;
  194.         selectedMenuItem = -1;
  195.     }
  196.  
  197.     void setSelectedVisibleMenuItem(int menuItem) {
  198.         topVisibleMenuItem = -1;
  199.         bottomVisibleMenuItem = -1;
  200.         selectedMenuItem = ((menuItem >= 0) && (menuItem <= popupMenu.getComponents().length - 1)) ? menuItem : 0;
  201.     }
  202.  
  203.  
  204.         //
  205.         //    Locate the first Fully Visible menu item below yPos
  206.         //
  207.     private int findFirstItemBelow(Component components[], int yPos)
  208.     {
  209.         int compindex;
  210.         for (compindex = firstRealMenuItem; compindex < lastRealMenuItem; compindex++) {
  211.             if (components[compindex].getLocation().y >= yPos)
  212.                 break;
  213.         }
  214.         
  215.         return compindex;
  216.     }
  217.  
  218.         //
  219.         //    Locate the last Fully Visible menu item above yPos
  220.         //
  221.     private int findLastItemAbove(Component components[], int yPos)
  222.     {
  223.         int compindex;
  224.         for (compindex = lastRealMenuItem; compindex > firstRealMenuItem; compindex--) {
  225.             if ((components[compindex].getLocation().y + components[compindex].getSize().height) <= yPos)
  226.                 break;
  227.         }
  228.         
  229.         return compindex;
  230.     }
  231.  
  232.     void adjustVisibleMenuItems(int lastTopVisibleMenuItem, int lastBottomVisibleMenuItem)
  233.     {
  234.         Component components[] = popupMenu.getComponents();
  235.         Component comp;
  236.  
  237.         yAdjustment = 0;
  238.  
  239.  
  240.         //
  241.         // Calculate adjustments for the outofrange components
  242.         //
  243.  
  244.             //
  245.             // We have defined a top visible menu item.  Make sure it is in view at the top.
  246.             //
  247.         if (topVisibleMenuItem >= 0)
  248.         {
  249.             if (topVisibleMenuItem > lastRealMenuItem)                                // Force it into range
  250.                 topVisibleMenuItem = lastRealMenuItem;
  251.  
  252.             comp = components[topVisibleMenuItem];
  253.  
  254.             // If this component isn't the first visible item,
  255.             //    Need to adjust components upward/downward so it is brought into view
  256.             yAdjustment = minY - comp.getLocation().y;
  257.             
  258.             if (topVisibleMenuItem > firstRealMenuItem) {
  259.                 yAdjustment += upScroller.getSize().height;
  260.                 
  261.                 // Adjusting for the scroller may have made enough room for the rest
  262.                 if (findFirstItemBelow(components, minY - yAdjustment) == firstRealMenuItem)
  263.                     topVisibleMenuItem = firstRealMenuItem;
  264.             }
  265.  
  266.             // determine which component is the bottom-most fully visible
  267.             bottomVisibleMenuItem = findLastItemAbove(components, maxY - yAdjustment);
  268.  
  269.             // If there are items below the bottom-most visible component, leave room for the scroller
  270.             if (bottomVisibleMenuItem < lastRealMenuItem)
  271.             {
  272.                 bottomVisibleMenuItem--;
  273.             }
  274.             // Otherwise, if the bottom-most visible component is the scroller (or after), don't bother with it
  275.             else if (bottomVisibleMenuItem > lastRealMenuItem)
  276.             {
  277.                 bottomVisibleMenuItem = lastRealMenuItem;
  278.             }
  279.  
  280.             if (bottomVisibleMenuItem < topVisibleMenuItem)
  281.                 bottomVisibleMenuItem = topVisibleMenuItem;
  282.         }
  283.  
  284.             //
  285.             // We have defined a bottom visible menu item.  Make sure it is in view at the bottom.
  286.             //
  287.         else if (bottomVisibleMenuItem >= 0)
  288.         {
  289.             if (bottomVisibleMenuItem > lastRealMenuItem)                            // Force it into range
  290.                 bottomVisibleMenuItem = lastRealMenuItem;
  291.  
  292.             comp = components[bottomVisibleMenuItem];
  293.  
  294.             // If this component isn't the last visible item,
  295.             //    Need to adjust components upward/downward so it is brought into view
  296.             yAdjustment = maxY - (comp.getLocation().y + comp.getSize().height);
  297.             
  298.             if (bottomVisibleMenuItem < lastRealMenuItem) {
  299.                 yAdjustment -= downScroller.getSize().height;
  300.                 
  301.                 // Adjusting for the scroller may have made enough room for the rest
  302.                 if (findLastItemAbove(components, maxY - yAdjustment) == lastRealMenuItem)
  303.                     bottomVisibleMenuItem = lastRealMenuItem;
  304.             }
  305.  
  306.             // determine which component is the top-most visible
  307.             topVisibleMenuItem = findFirstItemBelow(components, minY - yAdjustment);
  308.  
  309.             // If there are items above the top-most visible component, leave room for the scroller
  310.             if (topVisibleMenuItem > firstRealMenuItem)
  311.             {
  312.                 topVisibleMenuItem++;
  313.             }
  314.             // Otherwise, if the top-most visible component is the scroller (or before), don't bother with it
  315.             else if (topVisibleMenuItem < firstRealMenuItem)
  316.             {
  317.                 topVisibleMenuItem = firstRealMenuItem;
  318.             }
  319.  
  320.             if (topVisibleMenuItem > bottomVisibleMenuItem)
  321.                 topVisibleMenuItem = bottomVisibleMenuItem;
  322.         }
  323.  
  324.             //
  325.             // We have defined a selected visible menu item.  Make sure it is in view.
  326.             //    It just has to be in view, it doesn't have to be first or last.
  327.             //
  328.         else if (selectedMenuItem >= 0)
  329.         {
  330.             comp = components[topVisibleMenuItem];
  331.             int topVisibleMenuItemY = comp.getLocation().y;
  332.  
  333.             if (topVisibleMenuItemY < minY)
  334.             {
  335.                 // Selected component is too high.
  336.                 //    Need to adjust components downward so the selected component is brought into view
  337.                 yAdjustment = minY - topVisibleMenuItemY;
  338.  
  339.                 // If there are other selected items above this one, allow room for the UP arrow scroller
  340.                 if (topVisibleMenuItem > 1)
  341.                     yAdjustment += upScroller.getSize().height;
  342.             }
  343.             else if (topVisibleMenuItemY + comp.getSize().height > maxY)
  344.             {
  345.                 // Selected component is too low.
  346.                 //    Need to adjust components upward so the selected component is brought into view
  347.                 yAdjustment = maxY - (topVisibleMenuItemY + comp.getSize().height);
  348.  
  349.                 // If there are other selected items below this one, allow for a DOWN arrow scroller
  350.                 if (topVisibleMenuItem < (components.length - 2))
  351.                     yAdjustment -= downScroller.getSize().height;
  352.             }
  353.         }
  354.  
  355.  
  356.         //
  357.         // Adjust all items vertically, and adjust the newly out-of-view / newly in-view ones horizontally
  358.         //
  359.  
  360.         Point compLocation;
  361.  
  362.         for (int compindex = firstRealMenuItem; compindex <= lastRealMenuItem; compindex++) {
  363.             comp = components[compindex];
  364.             compLocation = comp.getLocation();
  365.             compLocation.y += yAdjustment;
  366.             
  367.             // If the item is not in view now, but was, hide it
  368.             if (compindex < topVisibleMenuItem || compindex > bottomVisibleMenuItem) {
  369.                 if (compindex >= lastTopVisibleMenuItem && compindex <= lastBottomVisibleMenuItem) {
  370.                     compLocation.x += nonVisibleXAdjustment;
  371.                 }
  372.             }
  373.             // If the item is in view now, but wasn't, show it
  374.             else if (compindex < lastTopVisibleMenuItem || compindex > lastBottomVisibleMenuItem) {
  375.                 compLocation.x -= nonVisibleXAdjustment;
  376.             }
  377.  
  378.             comp.setLocation(compLocation);
  379.         }
  380.         
  381.         // Are there more items above the topmost visible menu item
  382.         if (topVisibleMenuItem > firstRealMenuItem) {
  383.             if (lastTopVisibleMenuItem == firstRealMenuItem)
  384.                 upScroller.activateScroller();
  385.         }
  386.         // No longer need the up scroller
  387.         else if (lastTopVisibleMenuItem > firstRealMenuItem) {
  388.             upScroller.deactivateScroller();
  389.         }
  390.  
  391.         // Are there more items below the bottommost visible menu item
  392.         if (bottomVisibleMenuItem < lastRealMenuItem) {
  393.             if (lastBottomVisibleMenuItem == lastRealMenuItem)
  394.                 downScroller.activateScroller();
  395.         }
  396.         // No longer need the down scroller
  397.         else if (lastBottomVisibleMenuItem < lastRealMenuItem) {
  398.             downScroller.deactivateScroller();
  399.         }
  400.     }
  401.  
  402.  
  403.     void AdjustLayout() {
  404.         if (constrainedRect != null) {
  405.             // Allow for the up and down scrollers
  406.             lastRealMenuItem = popupMenu.getComponents().length - 3;
  407.  
  408.             Insets insets = popupMenu.getInsets();
  409.             minY = insets.top;
  410.             maxY = constrainedRect.height - (insets.top + insets.bottom);
  411.  
  412.             //
  413.             // Adjust all items vertically, and adjust the out-of-view / in-view ones horizontally
  414.             //
  415.             adjustVisibleMenuItems(firstRealMenuItem, lastRealMenuItem);
  416.         }
  417.     }
  418.  
  419.  
  420.     class MacPopupMenuLayout implements LayoutManager2 {
  421.  
  422.             //
  423.             //    LayoutManager2 overrides
  424.             //
  425.  
  426.         /**
  427.          * Adds the specified component to the layout, using the specified
  428.          * constraint object.
  429.          * @param comp the component to be added
  430.          * @param constraints  where/how the component is added to the layout.
  431.          */
  432.         public void addLayoutComponent(Component comp, Object constraints) {
  433.             originalLayoutManager.addLayoutComponent(comp, constraints);
  434.         }
  435.     
  436.         /** 
  437.          * Returns the maximum size of this component.
  438.          * @see java.awt.Component#getMinimumSize()
  439.          * @see java.awt.Component#getPreferredSize()
  440.          * @see LayoutManager
  441.          */
  442.         public Dimension maximumLayoutSize(Container target) {
  443.             return originalLayoutManager.maximumLayoutSize(target);
  444.         }
  445.     
  446.     
  447.         /**
  448.          * Returns the alignment along the x axis.  This specifies how
  449.          * the component would like to be aligned relative to other 
  450.          * components.  The value should be a number between 0 and 1
  451.          * where 0 represents alignment along the origin, 1 is aligned
  452.          * the furthest away from the origin, 0.5 is centered, etc.
  453.          */
  454.         public float getLayoutAlignmentX(Container target) {
  455.             return originalLayoutManager.getLayoutAlignmentX(target);
  456.         }
  457.         
  458.     
  459.         /**
  460.          * Returns the alignment along the y axis.  This specifies how
  461.          * the component would like to be aligned relative to other 
  462.          * components.  The value should be a number between 0 and 1
  463.          * where 0 represents alignment along the origin, 1 is aligned
  464.          * the furthest away from the origin, 0.5 is centered, etc.
  465.          */
  466.         public float getLayoutAlignmentY(Container target) {
  467.             return originalLayoutManager.getLayoutAlignmentY(target);
  468.         }
  469.         
  470.     
  471.         /**
  472.          * Invalidates the layout, indicating that if the layout manager
  473.          * has cached information it should be discarded.
  474.          */
  475.         public void invalidateLayout(Container target) {
  476.             originalLayoutManager.invalidateLayout(target);
  477.         }
  478.  
  479.  
  480.             //
  481.             //    LayoutManager2 overrides
  482.             //
  483.  
  484.         /**
  485.          * Adds the specified component with the specified name to
  486.          * the layout.
  487.          * @param name the component name
  488.          * @param comp the component to be added
  489.          */
  490.         public void addLayoutComponent(String name, Component comp) {
  491.             originalLayoutManager.addLayoutComponent(name, comp);
  492.         }
  493.         
  494.     
  495.         /**
  496.          * Removes the specified component from the layout.
  497.          * @param comp the component ot be removed
  498.          */
  499.         public void removeLayoutComponent(Component comp) {
  500.             originalLayoutManager.removeLayoutComponent(comp);
  501.         }
  502.  
  503.     
  504.         /**
  505.          * Calculates the preferred size dimensions for the specified 
  506.          * panel given the components in the specified parent container.
  507.          * @param parent the component to be laid out
  508.          *  
  509.          * @see #minimumLayoutSize
  510.          */
  511.         public Dimension preferredLayoutSize(Container parent) {
  512.             return originalLayoutManager.preferredLayoutSize(parent);
  513.         }
  514.  
  515.     
  516.         /** 
  517.          * Calculates the minimum size dimensions for the specified 
  518.          * panel given the components in the specified parent container.
  519.          * @param parent the component to be laid out
  520.          * @see #preferredLayoutSize
  521.          */
  522.         public Dimension minimumLayoutSize(Container parent) {
  523.             return originalLayoutManager.minimumLayoutSize(parent);
  524.         }
  525.  
  526.  
  527.         /** 
  528.          * Lays out the container in the specified panel.
  529.          * @param parent the component which needs to be laid out 
  530.          */
  531.         public void layoutContainer(Container parent) {
  532.             originalLayoutManager.layoutContainer(parent);
  533.             
  534.             AdjustLayout();
  535.         }
  536.     }
  537.  
  538.  
  539.         //===========================
  540.         //
  541.         //    Popup Menu scroller item methods
  542.         //
  543.         //===========================
  544.  
  545.  
  546.     abstract class PopupScrollerMenuItem extends JMenuItem implements ActionListener {
  547.         Timer scrollTimer = null;
  548.  
  549.         public PopupScrollerMenuItem(Icon scrollerIcon) {
  550.             super(" ", scrollerIcon);
  551.             setHorizontalTextPosition(JMenuItem.RIGHT);
  552.  
  553.             setEnabled(false);
  554.         }
  555.  
  556.         public void menuSelectionChanged(boolean isIncluded) {
  557.             if (isIncluded) {
  558.                 actionPerformed(null);
  559.             }
  560.             else {
  561.                 stopScroller();
  562.             }
  563.         }
  564.  
  565.         public void processMouseEvent(MouseEvent event,MenuElement path[],MenuSelectionManager manager) {
  566.             if (scrollTimer != null &&
  567.                 (event.getID() == MouseEvent.MOUSE_RELEASED || event.getID() == MouseEvent.MOUSE_EXITED)) {
  568.                 stopScroller();
  569.  
  570.                 if(path.length > 0 && path[path.length-1] == this) {
  571.                     MenuElement newPath[] = new MenuElement[path.length-1];
  572.                     System.arraycopy(path,0,newPath,0,path.length - 1);
  573.                     manager.setSelectedPath(newPath);
  574.                 }
  575.             }
  576.             super.processMouseEvent(event, path, manager);
  577.         }
  578.  
  579.         void startScroller() {
  580.             if (scrollTimer == null) {
  581.                 scrollTimer = new Timer(250, this);
  582.                 scrollTimer.setRepeats(false);
  583.                 scrollTimer.start();
  584.             } else {
  585.                 //
  586.                 //    The VM runs slow in MRJ.  Post a thread to restart the scrolling when we
  587.                 //    are done the current scroll.
  588.                 //
  589.                 Thread restartScroller = new Thread() {
  590.                     public void run() {
  591.                         if (scrollTimer != null)        // Might be null by now.
  592.                             scrollTimer.restart();
  593.                     }
  594.                 };
  595.                 restartScroller.setPriority(Thread.MIN_PRIORITY);
  596.                 restartScroller.start();
  597.  
  598. //                scrollTimer.restart();
  599.             }
  600.         }
  601.         
  602.         void stopScroller() {
  603.             if (scrollTimer != null) {
  604.                 scrollTimer.stop();
  605.                 scrollTimer = null;
  606.             }
  607.         }
  608.         
  609.         void activateScroller()
  610.         {
  611.             this.setLocation(getLocation().x, this.activeYLocation());
  612.             setEnabled(true);
  613.         }
  614.         void deactivateScroller()
  615.         {
  616.             stopScroller();
  617.  
  618.             this.setLocation(getLocation().x, -500);
  619.             setEnabled(false);
  620.         }
  621.  
  622.         /**
  623.          * Invoked when an action occurs.
  624.          */
  625.         public void actionPerformed(ActionEvent e) {
  626.             Scroll();
  627.  
  628.             startScroller();    // Force the timer to restart when the scroll is done.
  629.         }
  630.  
  631.  
  632.  
  633.         abstract void Scroll();
  634.         abstract int activeYLocation();
  635.     }
  636.  
  637.     class PopupDownScrollerMenuItem extends PopupScrollerMenuItem {
  638.         public PopupDownScrollerMenuItem() {
  639.             super(MacMenuUtilities.getPopupMenuDownScrollerArrowIcon());
  640.         }
  641.  
  642.         void Scroll() {
  643.             if (bottomVisibleMenuItem < lastRealMenuItem) {
  644.                 int lastTopVisibleMenuItem = topVisibleMenuItem;
  645.                 int lastBottomVisibleMenuItem = bottomVisibleMenuItem;
  646.     
  647.                 // Adjust the bottom visible menu item down by one
  648.                 topVisibleMenuItem = -1;
  649.                 bottomVisibleMenuItem++;
  650.  
  651.                 //
  652.                 // Calculate what items are now in range
  653.                 // Adjust all items vertically, and adjust the newly out-of-view / newly in-view ones horizontally
  654.                 //
  655.                 adjustVisibleMenuItems(lastTopVisibleMenuItem, lastBottomVisibleMenuItem);
  656.             }
  657.         }
  658.         int activeYLocation() {
  659.             return maxY - getSize().height;
  660.         }
  661.     }
  662.  
  663.     class PopupUpScrollerMenuItem extends PopupScrollerMenuItem {
  664.         public PopupUpScrollerMenuItem() {
  665.             super(MacMenuUtilities.getPopupMenuUpScrollerArrowIcon());
  666.         }
  667.  
  668.         void Scroll() {
  669.             if (topVisibleMenuItem > firstRealMenuItem) {
  670.                 int lastTopVisibleMenuItem = topVisibleMenuItem;
  671.                 int lastBottomVisibleMenuItem = bottomVisibleMenuItem;
  672.     
  673.                 // Adjust the top visible menu item up by one
  674.                 topVisibleMenuItem--;
  675.     
  676.                 //
  677.                 // Calculate what items are now in range
  678.                 // Adjust all items vertically, and adjust the newly out-of-view / newly in-view ones horizontally
  679.                 //
  680.                 adjustVisibleMenuItems(lastTopVisibleMenuItem, lastBottomVisibleMenuItem);
  681.             }
  682.         }
  683.  
  684.         int activeYLocation() {
  685.             return minY;
  686.         }
  687.     }
  688.  
  689.  
  690.         //===========================
  691.         //
  692.         //    PopupMenuListener methods
  693.         //
  694.         //===========================
  695.  
  696.  
  697.         //
  698.         //    popupMenuWillBecomeVisible
  699.         //
  700.         //    Just before the popup menu becomes visible, recalculate its location
  701.         //    and size, so that it fits into the maximum bounds for its location
  702.         //
  703.     public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
  704.         constrainedRect = null;
  705.  
  706.  
  707.  
  708.         //
  709.         //    Get the constraints for where the popup menu can/should appear
  710.         //
  711.         Rectangle maxBounds = new Rectangle();
  712.         Point preferredLocation = new Point();
  713.         getPopupConstraints(preferredLocation, maxBounds);
  714.  
  715.  
  716.         //
  717.         //    Determine if the menu fits within the maxbounds:
  718.         //
  719.         Rectangle preferredRect = new Rectangle(preferredLocation,
  720.                                                 scrollerLayoutManager.preferredLayoutSize(popupMenu));
  721.  
  722.         if (!SwingUtilities.isRectangleContainingRectangle(maxBounds, preferredRect))
  723.         {
  724.             // Simple adjustments: make the left and right sides within range
  725.             if (preferredRect.y < maxBounds.y)            // Too far up?
  726.                 preferredRect.y = maxBounds.y;
  727.             if (preferredRect.x < maxBounds.x)            // Too far left?
  728.                 preferredRect.x = maxBounds.x;
  729.  
  730.             //
  731.             // The Popup menu doesn't fit.  See if we can adjust the location of the menu
  732.             //        We can do this if the width and height are within bounds
  733.             //
  734.             if (maxBounds.width >= preferredRect.width &&
  735.                 maxBounds.height >= preferredRect.height)
  736.             {
  737.                 // If the menu doesn't fit vertically, adjust it
  738.                 int preferredBottom = preferredRect.y + preferredRect.height;
  739.                 int maxBottom = maxBounds.y + maxBounds.height;
  740.  
  741.                 if (preferredBottom > maxBottom)        // Too far down?
  742.                     preferredRect.y -= (preferredBottom - maxBottom);
  743.  
  744.  
  745.                 // If the menu doesn't fit horizontally, adjust it
  746.                 int preferredRight = preferredRect.x + preferredRect.width;
  747.                 int maxRight = maxBounds.x + maxBounds.width;
  748.  
  749.                 if (preferredRight > maxRight)            // Too far right?
  750.                     preferredRect.x -= (preferredRight - maxRight);
  751.             }
  752.             //
  753.             // Can't adjust location of the menu.  Adjust the size of the menu, and add scroller arrows.
  754.             //
  755.             else
  756.             {
  757.                 //
  758.                 // Calculate the viewable area of the popup menu
  759.                 //
  760.                 if (maxBounds.height < preferredRect.height)
  761.                 {
  762.                     // Make sure the scroller menu items appear at the correct locations
  763.                     popupMenu.add(upScroller);
  764.                     popupMenu.add(downScroller);
  765.  
  766.                     // Make the menu fill the available area vertically
  767.                     preferredRect.y = maxBounds.y;
  768.                     preferredRect.height = maxBounds.height;
  769.                     /*
  770.                     // Adjust the menu item selection to account for the scrollers
  771.                     if (topVisibleMenuItem >= 0)
  772.                         topVisibleMenuItem++;
  773.                     else if (bottomVisibleMenuItem >= 0)
  774.                         bottomVisibleMenuItem++;
  775.                     else if (selectedMenuItem >= 0)
  776.                         selectedMenuItem++;
  777.                     */
  778.                 }
  779.  
  780.                 if (maxBounds.width < preferredRect.width)
  781.                 {
  782.                     // Make the menu fill the available area horizontally
  783.                     preferredRect.x = maxBounds.x;
  784.                     preferredRect.width = maxBounds.width;
  785.                 }
  786.  
  787.                 popupMenu.invalidate();
  788.  
  789.                 constrainedRect = preferredRect;
  790.                 
  791.                 //
  792.                 //    Now, run through the menu items, to see which ones fit within the viewable area
  793.                 //
  794.             }
  795.  
  796.             popupMenu.setLocation(preferredRect.x, preferredRect.y);
  797.         }
  798.  
  799.         popupMenu.validate();
  800.     }
  801.  
  802.     public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
  803.         constrainedRect = null;
  804.  
  805.         // Remove the scroller menu items
  806.         popupMenu.remove(upScroller);
  807.         popupMenu.remove(downScroller);
  808.  
  809.         setTopVisibleMenuItem(0);
  810.     }
  811.  
  812.     public void popupMenuCanceled(PopupMenuEvent e) {
  813.     }
  814.  
  815. }
  816.